<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="pKyzy" id="pKyzy"><span data-lake-id="u4b227f69" id="u4b227f69">典型回答</span></h1>
  <p data-lake-id="uaabc9508" id="uaabc9508"><strong><span data-lake-id="u151ffcb7" id="u151ffcb7">MyBatis-Plus支持分页插件——PaginationInnerInterceptor</span></strong></p>
  <p data-lake-id="ua6ad9065" id="ua6ad9065"><strong><span data-lake-id="u4e175938" id="u4e175938">​</span></strong><br></p>
  <p data-lake-id="u60b74cf0" id="u60b74cf0"><strong><span data-lake-id="u318372b6" id="u318372b6">PaginationInnerInterceptor采用的是物理分页方式，物理分页是在数据库中进行分页，即直接在SQL语句中加入LIMIT语句，只查询所需的部分数据。</span></strong></p>
  <p data-lake-id="ud456d877" id="ud456d877"><span data-lake-id="u5c7550dd" id="u5c7550dd">​</span><br></p>
  <p data-lake-id="ue5bedaa3" id="ue5bedaa3"><span data-lake-id="ubaa909a8" id="ubaa909a8">物理分页的优点是可以减少内存占用，减轻数据库的负载，缺点是无法对结果进行任意操作，比如说在分页过程中做二次过滤、字段映射、json解析等。</span></p>
  <p data-lake-id="u0f62a1da" id="u0f62a1da"><br></p>
  <p data-lake-id="u6930ef38" id="u6930ef38"><a href="https://github.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/PaginationInnerInterceptor.java" target="_blank" data-lake-id="u9ab1b013" id="u9ab1b013"><strong><span data-lake-id="u2b5e00be" id="u2b5e00be">PaginationInnerInterceptor</span></strong></a><span data-lake-id="u526a4a63" id="u526a4a63">这个分页插件就会自动拦截所有的SQL查询请求，计算分页查询的起始位置和记录数，并在SQL语句中加入LIMIT语句。</span></p>
  <p data-lake-id="u51209ce2" id="u51209ce2"><span data-lake-id="ua8ae0318" id="ua8ae0318">​</span><br></p>
  <p data-lake-id="u8a4ef2a7" id="u8a4ef2a7"><span data-lake-id="u1b1edc82" id="u1b1edc82">核心的操作在beforeQuery中：</span></p>
  <p data-lake-id="u6048838b" id="u6048838b"><span data-lake-id="ue0003838" id="ue0003838">​</span><br></p>
  <pre lang="java"><code>

@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    IPage&lt;?&gt; page = ParameterUtils.findPage(parameter).orElse(null);
    if (null == page) {
        return;
    }

    // 处理 orderBy 拼接
    boolean addOrdered = false;
    String buildSql = boundSql.getSql();
    List&lt;OrderItem&gt; orders = page.orders();
    if (CollectionUtils.isNotEmpty(orders)) {
        addOrdered = true;
        buildSql = this.concatOrderBy(buildSql, orders);
    }

    // size 小于 0 且不限制返回值则不构造分页sql
    Long _limit = page.maxLimit() != null ? page.maxLimit() : maxLimit;
    if (page.getSize() &lt; 0 &amp;&amp; null == _limit) {
        if (addOrdered) {
            PluginUtils.mpBoundSql(boundSql).sql(buildSql);
        }
        return;
    }

    handlerLimit(page, _limit);
    IDialect dialect = findIDialect(executor);

    final Configuration configuration = ms.getConfiguration();
    DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());
    PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);

    List&lt;ParameterMapping&gt; mappings = mpBoundSql.parameterMappings();
    Map&lt;String, Object&gt; additionalParameter = mpBoundSql.additionalParameters();
    model.consumers(mappings, configuration, additionalParameter);
    mpBoundSql.sql(model.getDialectSql());
    mpBoundSql.parameterMappings(mappings);
}

</code></pre>
  <p data-lake-id="u050a5e58" id="u050a5e58"><span data-lake-id="uff616d05" id="uff616d05">​</span><br></p>
  <p data-lake-id="u81453375" id="u81453375"><span data-lake-id="u36a17e9d" id="u36a17e9d">其中比较关键的就是第31行，buildPaginationSql方法。这里不同的数据库有不同的实现，我们看一下MySQL的实现：</span></p>
  <p data-lake-id="u7fd90b9d" id="u7fd90b9d"><span data-lake-id="uecb73408" id="uecb73408">​</span><br></p>
  <pre lang="java"><code>
public class MySqlDialect implements IDialect {

    @Override
    public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
        StringBuilder sql = new StringBuilder(originalSql).append(" LIMIT ").append(FIRST_MARK);
        if (offset != 0L) {
            sql.append(StringPool.COMMA).append(SECOND_MARK);
            return new DialectModel(sql.toString(), offset, limit).setConsumerChain();
        } else {
            return new DialectModel(sql.toString(), limit).setConsumer(true);
        }
    }
}
</code></pre>
  <p data-lake-id="u95fe7e52" id="u95fe7e52"><br></p>
  <p data-lake-id="u2473bbf0" id="u2473bbf0"><span data-lake-id="u789f639c" id="u789f639c">这段代码就比较好理解了，其实就是在原来的SQL后面拼上</span><code data-lake-id="u01d840f5" id="u01d840f5"><span data-lake-id="u1d255d37" id="u1d255d37">LIMIT ?,?</span></code><span data-lake-id="ud9eea210" id="ud9eea210"> ，这样在后续执行的过程中，就可以把offerset和limit赋值给这两个占位符，实现分页查询了。</span></p>
  <p data-lake-id="u9e7490fc" id="u9e7490fc"><span data-lake-id="u994ca862" id="u994ca862">​</span><br></p>
  <h1 data-lake-id="p64cS" id="p64cS"><span data-lake-id="u96eb6598" id="u96eb6598">扩展知识</span></h1>
  <p data-lake-id="u55d35a4f" id="u55d35a4f"><br></p>
  <h2 data-lake-id="UPEFD" id="UPEFD"><span data-lake-id="ua50f2bdc" id="ua50f2bdc">使用方法</span></h2>
  <p data-lake-id="u0e725733" id="u0e725733"><br></p>
  <p data-lake-id="u31606944" id="u31606944"><span data-lake-id="u8985462c" id="u8985462c">使用mybatis-plus实现分页，挺简单的，参考以下步骤：</span></p>
  <p data-lake-id="ub15cb207" id="ub15cb207"><span data-lake-id="ud0142d88" id="ud0142d88">​</span><br></p>
  <p data-lake-id="u88b11a76" id="u88b11a76" style="text-align: left"><strong><span data-lake-id="u13954ffd" id="u13954ffd">添加分页插件</span></strong><span data-lake-id="uc31caff0" id="uc31caff0">：首先，在 MyBatis-Plus 的配置中添加分页插件，在 Spring Boot 应用中，可以这样配置：</span></p>
  <p data-lake-id="uab8221ec" id="uab8221ec" style="text-align: left"><span data-lake-id="ue18c5188" id="ue18c5188" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">​</span><br></p>
  <pre lang="java"><code>
@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
        return interceptor;
    }
}
</code></pre>
  <p data-lake-id="u3b262c4e" id="u3b262c4e"><br></p>
  <p data-lake-id="u51ed099d" id="u51ed099d"><strong><span data-lake-id="ucc0ebfe3" id="ucc0ebfe3">编写 Mapper 接口</span></strong><span data-lake-id="u009f170d" id="u009f170d">：定义一个 Mapper 接口，用于执行数据库操作。这个接口不需要特别指定分页相关的方法，MyBatis-Plus 会自动处理。</span></p>
  <p data-lake-id="ub4db06bd" id="ub4db06bd"><span data-lake-id="u1340e028" id="u1340e028" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">​</span><br></p>
  <pre lang="java"><code>
public interface UserMapper extends BaseMapper&lt;User&gt; {
    // 这里可以添加其他数据库操作的方法
}
</code></pre>
  <p data-lake-id="u18d888e9" id="u18d888e9"><br></p>
  <p data-lake-id="u9acd53c1" id="u9acd53c1"><strong><span data-lake-id="u94dff1e1" id="u94dff1e1">执行分页查询</span></strong><span data-lake-id="u2c397794" id="u2c397794">：在服务层或者控制器层，使用 MyBatis-Plus 提供的 Page 类来执行分页查询。例如，要查询第 1 页的数据，每页显示 10 条记录，可以这样写：</span></p>
  <p data-lake-id="ud785d922" id="ud785d922"><span data-lake-id="u4df1f85e" id="u4df1f85e">​</span><br></p>
  <pre lang="java"><code>
@Autowired
private UserMapper userMapper;

public IPage&lt;User&gt; selectUserPage(int currentPage, int pageSize) {
    Page&lt;User&gt; page = new Page&lt;&gt;(currentPage, pageSize);
    IPage&lt;User&gt; userPage = userMapper.selectPage(page, null);
    return userPage;
}
</code></pre>
  <p data-lake-id="ue81c6dcc" id="ue81c6dcc"><span data-lake-id="ue5cebaa5" id="ue5cebaa5">​</span><br></p>
  <p data-lake-id="u72e2e7c1" id="u72e2e7c1"><span data-lake-id="u2c714fb8" id="u2c714fb8">selectPage 方法是 MyBatis-Plus 提供的内置方法，用于执行分页查询。null 作为第二个参数表示没有查询条件，即查询所有记录。</span></p>
  <p data-lake-id="ua996db62" id="ua996db62"><span data-lake-id="u2602822a" id="u2602822a">​</span><br></p>
  <p data-lake-id="ud57db521" id="ud57db521"><span data-lake-id="ubfc31b7a" id="ubfc31b7a">selectPage 方法返回的 IPage 对象包含了分页信息（如当前页码、总页数、每页记录数、总记录数等）和查询结果。</span></p>
  <p data-lake-id="ua7ff8fd5" id="ua7ff8fd5"><span data-lake-id="u0b2d94ab" id="u0b2d94ab">​</span><br></p>
  <p data-lake-id="u623a452e" id="u623a452e"><span data-lake-id="u5c6c12aa" id="u5c6c12aa">总页数、总记录数等的统计是在PaginationInnerInterceptor的willDoQuery方法中实现的，具体大家自行看下代码就可以了，比较容易理解，这里就不展开了。</span></p>
 </body>
</html>